﻿using System;
using System.Threading.Tasks;
using BDSA12;

namespace GameOfLife {

    /// <summary>
    /// Class representing a Game-of-Life instance
    /// </summary>
    class Game : IGameOfLife {
        private int?[,] board;
        private CellState[] cells;
        //size of the board
        private int N; //variable for sharing size among methods
        private Random rand = new Random();
        

        /// <summary>
        /// Constructor
        /// Creates the board with n * n cells. The cell-struct are given a x,y value and a cell-state.
        /// </summary>
        /// <param name="size">Size of the board</param>
        public Game(int size) {
            N = size; //assign size to class field N
            board = new int?[size, size];
            cells = new CellState[size * size];
            int k = 0;
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    CellState cell = new CellState(j, i, rand.Next(0, 2));
                    cells[k] = cell;
                    k++;
                }
            }
            cells[0].state = null;
            fillBoard();
            
        }

        /// <summary>
        /// Overloads the main constructor, giving the abillity
        /// to define a custom array
        /// </summary>
        /// <param name="size"></param>
        /// <param name="preDefBoard"></param>
        public Game(int size, int?[,] preDefBoard) : this(size) {
            int k = 0;
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    CellState cell = new CellState(j, i, preDefBoard[i,j]);
                    cells[k] = cell;
                    k++;
                }
            }
            fillBoard();
        }

        private void fillBoard() {
            foreach (CellState cs in cells) {
                board[cs.x, cs.y] = cs.state;
            }
        }

        /// <summary>
        /// Changes the state of the cell, depending on the state of its neighbours.
        /// </summary>
        /// <param name="cells">Cells in the array</param>
        internal void ChangeState(params CellState[] cells) {

            //AWESOME Parallel for-loop! - wuuhu :)
            Parallel.For(0, cells.Length, i => {
                //Create a temp cell to modify
                CellState tempCell = cells[i];
                //is any nabo cells a zombie
                bool NaboZombie = false;
                int? nabo = CountNabo(ref NaboZombie, tempCell.x, tempCell.y);
                //if zombie
                if (tempCell.state == null) {
                    //do nothing
                }
                //DEAD
                //Rule #5 - A dead cell with 3 neighbors is come to life
                else if (tempCell.state == 0) {
                    if (nabo == 3) { 
                        tempCell.state = 1;
                        //Copy the cell back into the array
                        cells[i] = tempCell;
                    }
                }

                //ALIVE
                //Rule #1 - A live cell with 1 or less neighbors dies
                else if (nabo <= 1) {
                    tempCell.state = 0;
                    //Copy the cell back into the array
                    cells[i] = tempCell;
                }
                //Rule #3 - A live cell with 4 or more neighbors dies
                else if (nabo >= 4) {
                    tempCell.state = 0;
                    //Copy the cell back into the array
                    cells[i] = tempCell;
                }
                //Rule #4 - A live cell with 1 or more zombie  neighbors has a 50% chance of surviving or becoming a zombie
                else if (NaboZombie == true) {
                    double ran = rand.NextDouble();
                    if (ran < 0.50) {
                        tempCell.state = null;
                        //Copy the cell back into the array
                        cells[i] = tempCell;
                    }
                }
                else {
                    tempCell.state = 1;
                    //Copy the cell back into the array
                    cells[i] = tempCell;
                }
            });
            // update the board
            foreach (CellState cs in cells) {
                board[cs.x, cs.y] = cs.state;
            }
        }

        internal CellState[] getCells() {
            return this.cells;
        }

        /// <summary>
        /// Draws the board in the cmd from the cell array. Changes the line in the cmd
        /// if the counter % N is equal to zero.
        /// </summary>
        internal void draw() {
            int counter = 1;
            string write;
            foreach (int? cell in board) {
                if (cell == null) write = "Z";
                else write = "" + cell;
                if (counter % N == 0) Console.WriteLine(write);
                else Console.Write(write + " ");
                counter++;
            }
        }

        /// <summary>
        /// Counts the neighbours to the given cell. (STILL VALID? If the neighbour cell is out of
        /// the board index, a IndexOutOfRangeException is caught).
        /// </summary>
        /// <param name="x">x value</param>
        /// <param name="y">y value</param>
        /// <returns>
        /// Return is number of alive neighbours as an int
        /// if one of t he neighbours is a zombie return -1
        /// </returns>
        internal int? CountNabo(ref bool zombie, int x, int y) {
            int counter = 0;
            zombie = false;
            for (int i = -1; i < 2; i++) {
                //What cell to look at the x-cordinate
                int tempX = x + i;
                //the following implements the wrap-around/fall-through
                if (tempX == -1) tempX = tempX + N;
                if (tempX == N) tempX = 0;
                for (int j = -1; j < 2; j++) {
                    //What cell to look at the y-cordinate
                    int tempY = y + j;
                    //the following implements the wrap-around/fall-through
                    if (tempY == -1) tempY = tempY + N;
                    if (tempY == N) tempY = 0;
                    //dont look at the middel cell
                    if (i == 0 && j == 0) continue;
                    //if the cell is a zombie
                    if (board[tempX, tempY] == null)
                        zombie = true;
                    //if the cell is alive
                    if (board[tempX, tempY] == 1)
                        counter++;
                }
            }
            return counter;
        }

        /// <summary>
        /// The struct of a cell. It contains a contructor which initialize
        /// the x,y and state value.
        /// </summary>
        internal struct CellState {

            public CellState(int x, int y, int? state)
                : this() {
                this.x = x;
                this.y = y;
                this.state = state;
            }

            public int x {
                set;
                get;
            }
            public int y {
                set;
                get;
            }
            public int? state {
                set;
                get;
            }
        }

        //interface implmentaion
        //change the state of the cells
        public void NextDay() {
            ChangeState(cells);
        }

        //interface implmentaion
        //returns the size of the board
        public uint Size {
            get { return (uint)N; }
        }

        //interface implmentaion
        //returns the state of an array position
        public int? this[uint col, uint row] {
            get { return board[col, row]; }
        }
    }
}